﻿using System;
using System.Reflection;
using System.Collections.Generic;
using gbL.NetBoard.Filters;
using gbL.NetBoard.Testing;

namespace gbL.NetBoard.Broadcast
{
    public class Broadcaster : IFilteringBroadcaster, IBroadcasterTestSeams
    {
        public Broadcaster()
        {
            _addedFilters = new FilterSet();;
            _removedFilters = new FilterSet();
        }

        // IBroadcast

        public void NotifyAdded(object blackboard, IBlackboardEntry addedEntry)
        {
            if (Added != null)
            {
                Delegate[] eventRegistrations = Added.GetInvocationList();
                FilterAndInvokeForEntry(
                    blackboard, 
                    addedEntry, 
                    eventRegistrations, 
                    _addedFilters.ToArray()
                );
            }
        }

        public void NotifyRemoved(object blackboard, params IBlackboardEntry[] removedEntries)
        {
            if (Removed != null && removedEntries != null)
            {
                foreach (IBlackboardEntry entry in removedEntries)
                {
                    Delegate[] eventRegistrations = Removed.GetInvocationList();
                    FilterAndInvokeForEntry(
                        blackboard, 
                        entry, 
                        eventRegistrations, 
                        _removedFilters.ToArray()
                    );
                }
            }
        }

        public event EventHandler<ChangedEventArgs> Added;

        public event EventHandler<ChangedEventArgs> Removed;

        // IFilterBroadcasts

        public void FilterAddedByType(object registrant, Type messageType)
        {
            _addedFilters.MessageTypeFilter.AddFilter(registrant, messageType);
        }

        public void FilterRemovedByType(object registrant, Type messageType)
        {
            _removedFilters.MessageTypeFilter.AddFilter(registrant, messageType);
        }

        public void FilterAddedByTag(object registrant, object tag)
        {
            _addedFilters.TagFilter.AddFilter(registrant, tag);
        }

        public void FilterRemovedByTag(object registrant, object tag)
        {
            _removedFilters.TagFilter.AddFilter(registrant, tag);
        }

        public void FilterAddedByFunc(object registrant, Func<IBlackboardEntry, bool> test)
        {
            _addedFilters.EntryFuncFilter.AddFilter(registrant, test);
        }

        public void FilterRemovedByFunc(object registrant, Func<IBlackboardEntry, bool> test)
        {
            _removedFilters.EntryFuncFilter.AddFilter(registrant, test);
        }

        public void FilterAddedByFrom(object registrant, object from)
        {
            _addedFilters.FromFilter.AddFilter(registrant, from);
        }

        public void FilterRemovedByFrom(object registrant, object from)
        {
            _removedFilters.FromFilter.AddFilter(registrant, from);
        }

        public void RemoveFilterAddedByType(object registrant, Type messageType)
        {
            throw new NotImplementedException();
        }

        public void RemoveFilterRemovedByType(object registrant, Type messageType)
        {
            throw new NotImplementedException();
        }

        public void RemoveFilterAddedByTag(object registrant, object tag)
        {
            throw new NotImplementedException();
        }

        public void RemoveFilterRemovedByTag(object registrant, object tag)
        {
            throw new NotImplementedException();
        }

        public void RemoveFilterAddedByFunc(object registrant, Func<IBlackboardEntry, bool> test)
        {
            throw new NotImplementedException();
        }

        public void RemoveFilterRemovedByFunc(object registrant, Func<IBlackboardEntry, bool> test)
        {
            throw new NotImplementedException();
        }

        public void RemoveFilterAddedByFrom(object registrant, object from)
        {
            throw new NotImplementedException();
        }

        public void RemoveFilterRemovedByFrom(object registrant, object from)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Testing or extensibility seam only
        /// </summary>
        /// <returns></returns>
        protected Delegate[] GetInvocationListAdded()
        {
            if (Added == null) return new Delegate[0];
            return Added.GetInvocationList();
        }

        /// <summary>
        /// Testing or extensibility seam only
        /// </summary>
        /// <returns></returns>
        protected Delegate[] GetInvocationListRemoved()
        {
            if (Removed == null) return new Delegate[0];
            return Removed.GetInvocationList();
        }

        private FilterSet _addedFilters;

        private FilterSet _removedFilters;

        private static void FilterAndInvokeForEntry(object blackboard, IBlackboardEntry entry, Delegate[] eventRegistrations, IEnumerable<IReadOnlyEntryFilter> filters)
        {
            EventArgs eventArgs = new ChangedEventArgs(entry);

            foreach (IReadOnlyEntryFilter filter in filters) filter.Precalculate(entry);

            foreach (Delegate registration in eventRegistrations)
            {
                object target = registration.Target;

                bool shouldSend = true;
                
                if (target != null)
                {
                    foreach (IReadOnlyEntryFilter filter in filters)
                    {
                        if (filter.HasFilter(target))
                        {
                            shouldSend &= filter.IsAllowed(target, entry);
                        }
                    }
                }

                if (shouldSend)
                {
                    InvokeAndManageException(
                        () => registration.DynamicInvoke(blackboard, eventArgs)
                    );
                }
            }
        }

        private static void InvokeAndManageException(Action action)
        {
            try
            {
                action();
            }
            catch (TargetInvocationException tiex)
            {
                throw new BlackboardListenerException(tiex);
            }
        }

        #region IBroadcasterTestSeams Members

        Delegate[] gbL.NetBoard.Testing.IBroadcasterTestSeams.GetInvocationListAdded()
        {
            return GetInvocationListAdded();
        }

        Delegate[] gbL.NetBoard.Testing.IBroadcasterTestSeams.GetInvocationListRemoved()
        {
            return GetInvocationListRemoved();
        }

        #endregion
    }
}

